/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.zzdt4j.core.executor.support;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.zzdt4j.common.executor.support.BlockingQueueTypeEnum;
import cn.zzdt4j.common.extension.design.Builder;
import org.springframework.core.task.TaskDecorator;

import java.math.BigDecimal;
import java.util.Optional;
import java.util.concurrent.*;

/**
 * Thread-pool builder
 *
 * @author by <a href="mailto:ligang941012@gmail.com">gang.Li</a>
 * @since 2023/10/19 22:27
 */
public class ThreadPoolBuilder implements Builder<ThreadPoolExecutor> {

    private static ThreadPoolBuilder _self;

    private boolean isFastPool;
    private boolean isDynamicPool;

    private int corePoolSize = calcCoreNum();

    private int maximumPoolSize = corePoolSize + (corePoolSize >> 1);

    private long keepAliveTime = 30000L;

    private TimeUnit timeUnit = TimeUnit.MILLISECONDS;

    private long executeTimeOut = 1000L;

    private int capacity = 512;

    private BlockingQueueTypeEnum blockingQueueType = BlockingQueueTypeEnum.LINKED_BLOCKING_QUEUE;

    private BlockingQueue workQueue;

    private RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.AbortPolicy();

    private boolean isDaemon = false;

    private String threadNamePrefix;

    private ThreadFactory threadFactory;

    private String threadPoolId;

    /**
     * Dynamic thread pool delivery context.
     */
    private TaskDecorator taskDecorator;

    /**
     * The time to wait for the task to complete in milliseconds.
     */
    private Long awaitTerminationMillis = 5000L;

    /**
     * Whether to wait for tasks to complete when closing the thread pool, default is true
     */
    private Boolean waitForTasksToCompleteOnShutdown = true;

    private Boolean allowCoreThreadTimeOut = false;

    /**
     * Calculate core num.
     *
     * @return core num
     */
    private Integer calcCoreNum() {
        int cpuCoreNum = Runtime.getRuntime().availableProcessors();
        return new BigDecimal(cpuCoreNum).divide(new BigDecimal("0.2")).intValue();
    }

    /**
     * Is fast pool.
     *
     * @param isFastPool Is fast pool
     * @return thread-pool builder
     */
    public ThreadPoolBuilder isFastPool(Boolean isFastPool) {
        this.isFastPool = isFastPool;
        return _self;
    }

    /**
     * Dynamic thread-pool.
     *
     * @return thread-pool builder
     */
    public ThreadPoolBuilder dynamicPool() {
        this.isDynamicPool = true;
        return _self;
    }

    /**
     * Thread factory.
     *
     * @param threadNamePrefix thread name prefix
     * @return thread-pool builder
     */
    public ThreadPoolBuilder threadFactory(String threadNamePrefix) {
        this.threadNamePrefix = threadNamePrefix;
        return _self;
    }

    public ThreadPoolBuilder threadFactory(ThreadFactory threadFactory) {
        this.threadFactory = threadFactory;
        return _self;
    }

    public ThreadPoolBuilder threadFactory(String threadNamePrefix, Boolean isDaemon) {
        this.threadNamePrefix = threadNamePrefix;
        this.isDaemon = isDaemon;
        return _self;
    }

    public ThreadPoolBuilder corePoolSize(int corePoolSize) {
        this.corePoolSize = corePoolSize;
        return _self;
    }

    public ThreadPoolBuilder maximumPoolSize(int maximumPoolSize) {
        this.maximumPoolSize = maximumPoolSize;
        // safe set this maximumPoolSize. maximumPoolSize must be more than coreNum
        if (maximumPoolSize < this.corePoolSize) {
            this.corePoolSize = maximumPoolSize;
        }
        return _self;
    }

    public ThreadPoolBuilder singlePool() {
        this.corePoolSize = 1;
        this.maximumPoolSize = 1;
        return _self;
    }

    public ThreadPoolBuilder singlePool(String threadNamePrefix) {
        this.corePoolSize = 1;
        this.maximumPoolSize = 1;
        this.threadNamePrefix = threadNamePrefix;
        return _self;
    }

    public ThreadPoolBuilder poolThreadSize(int corePoolSize, int maximumPoolSize) {
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        return _self;
    }

    public ThreadPoolBuilder keepAliveTime(long keepAliveTime) {
        this.keepAliveTime = keepAliveTime;
        return _self;
    }

    public ThreadPoolBuilder timeUnit(TimeUnit timeUnit) {
        this.timeUnit = timeUnit;
        return _self;
    }

    public ThreadPoolBuilder executeTimeOut(long executeTimeOut) {
        this.executeTimeOut = executeTimeOut;
        return _self;
    }

    public ThreadPoolBuilder keepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
        this.keepAliveTime = keepAliveTime;
        this.timeUnit = timeUnit;
        return _self;
    }

    public ThreadPoolBuilder capacity(int capacity) {
        this.capacity = capacity;
        return _self;
    }

    public ThreadPoolBuilder workQueue(BlockingQueueTypeEnum queueType, int capacity) {
        this.blockingQueueType = queueType;
        this.capacity = capacity;
        return _self;
    }

    public ThreadPoolBuilder rejected(RejectedExecutionHandler rejectedExecutionHandler) {
        this.rejectedExecutionHandler = rejectedExecutionHandler;
        return _self;
    }

    public ThreadPoolBuilder workQueue(BlockingQueueTypeEnum queueType) {
        this.blockingQueueType = queueType;
        return _self;
    }

    public ThreadPoolBuilder workQueue(BlockingQueue workQueue) {
        this.workQueue = workQueue;
        return _self;
    }

    public ThreadPoolBuilder threadPoolId(String threadPoolId) {
        this.threadPoolId = threadPoolId;
        return _self;
    }

    public ThreadPoolBuilder taskDecorator(TaskDecorator taskDecorator) {
        this.taskDecorator = taskDecorator;
        return _self;
    }

    public ThreadPoolBuilder awaitTerminationMillis(Long awaitTerminationMillis) {
        this.awaitTerminationMillis = awaitTerminationMillis;
        return _self;
    }

    public ThreadPoolBuilder waitForTasksToCompleteOnShutdown(Boolean waitForTasksToCompleteOnShutdown) {
        this.waitForTasksToCompleteOnShutdown = waitForTasksToCompleteOnShutdown;
        return _self;
    }

    public ThreadPoolBuilder dynamicSupport(boolean waitForTasksToCompleteOnShutdown, long awaitTerminationMillis) {
        this.awaitTerminationMillis = awaitTerminationMillis;
        this.waitForTasksToCompleteOnShutdown = waitForTasksToCompleteOnShutdown;
        return _self;
    }

    public ThreadPoolBuilder allowCoreThreadTimeOut(Boolean allowCoreThreadTimeOut) {
        this.allowCoreThreadTimeOut = allowCoreThreadTimeOut;
        return _self;
    }

    public static ThreadPoolBuilder builder() {
        return _self = new ThreadPoolBuilder();
    }

    public static ThreadPoolExecutor buildDynamicPoolById(String threadPoolId) {
        return ThreadPoolBuilder.builder().threadFactory(threadPoolId).threadPoolId(threadPoolId).dynamicPool().build();
    }

    private static ThreadPoolExecutor buildPool(ThreadPoolBuilder builder) {
        return AbstractBuildThreadPoolTemplate.buildPool(buildInitParam(builder));
    }

    private ThreadPoolExecutor buildDynamicPool(ThreadPoolBuilder builder) {
        return AbstractBuildThreadPoolTemplate.buildDynamicPool(buildInitParam(builder));
    }

    /**
     * Build thread-pool initialization parameters via builder.
     *
     * @param builder thread-pool builder
     * @return thread-pool init param
     */
    private static AbstractBuildThreadPoolTemplate.ThreadPoolInitParam buildInitParam(ThreadPoolBuilder builder) {
        AbstractBuildThreadPoolTemplate.ThreadPoolInitParam initParam;
        if (ObjectUtil.isNull(builder.threadFactory)) {
            Assert.notEmpty(builder.threadNamePrefix,
                    "The thread name prefix cannot be empty or an empty string.                                                                                                                                                                                      ");
            initParam = new AbstractBuildThreadPoolTemplate.ThreadPoolInitParam(builder.threadNamePrefix, builder.isDaemon);
        } else {
            initParam = new AbstractBuildThreadPoolTemplate.ThreadPoolInitParam(builder.threadFactory);
        }

        initParam.setCorePoolNum(builder.corePoolSize)
                .setMaximumPoolSize(builder().maximumPoolSize)
                .setKeepAliveTime(builder.keepAliveTime)
                .setCapacity(builder.capacity)
                .setExecuteTimeOut(builder.executeTimeOut)
                .setRejectedExecutionHandler(builder.rejectedExecutionHandler)
                .setTimeUnit(builder.timeUnit)
                .setAllowCoreThreadTimeOut(builder.allowCoreThreadTimeOut)
                .setTaskDecorator(builder.taskDecorator);
        if (builder.isDynamicPool) {
            final String threadPoolId = Optional.ofNullable(builder.threadPoolId).orElse(builder.threadNamePrefix);
            initParam.setThreadPoolId(threadPoolId);
            initParam.setWaitForTasksToCompleteOnShutdown(builder.waitForTasksToCompleteOnShutdown);
            initParam.setAwaitTerminationMillis(builder.awaitTerminationMillis);
        }

        if (!builder.isFastPool) {
            if (ObjectUtil.isNull(builder.workQueue)) {
                if (ObjectUtil.isNull(builder.blockingQueueType)) {
                    builder.blockingQueueType = BlockingQueueTypeEnum.LINKED_BLOCKING_QUEUE;
                }
                builder.workQueue = BlockingQueueTypeEnum.createBlockingQueue(builder.blockingQueueType.getType(), builder.capacity);
            }
            initParam.setWorkQueue(builder.workQueue);
        }

        return initParam;
    }

    @Override
    public ThreadPoolExecutor build() {
        return isDynamicPool ? buildDynamicPool(_self) : buildPool(_self);
    }

    static {
        _self = new ThreadPoolBuilder();
    }
}
